﻿using System;
using System.Collections.Generic;
using System.Linq;
using CardPlay.Model.ActionModel;
using CardPlay.Model.CardModel;
using CardPlay.Model.GameModel;
using CardPlay.Model.GameModel.Actions;
using CardPlay.Model.GameModel.Presentation;
using CardPlay.Model.Layout;
using CardPlay.Model.Primitives;
using Action = CardPlay.Model.GameModel.Actions.Action;

namespace CardPlay.DemoPack.Games
{
    [Game("2DF0A592-3935-4158-96F2-D21FB6F8C04F", LevelOfDifficulty.Medium, LevelOfSkill.ModerateSkill, DurationOfPlay.Within5To10Minutes)]
    public class Fission : StandardCardGame
    {
        private class PileBehavior
        {
            public Func<CardPile, LayoutMetrics, Size[]> Arrangement;
            public Predicate<RemovePileAction> AllowsPickup;
            public Predicate<MovePileAction> AllowsMove;
        }

        private static readonly Size ReserveSize = new Size(7, 4);

        private readonly Dictionary<CardPile, PileBehavior> _reserveBehaviors = new Dictionary<CardPile, PileBehavior>();
        private readonly PileBehavior _dividingBehavior;
        private readonly PileBehavior _foundationBehavior;

        public Fission()
        {
            NamesOfAdditionalPiles.Remove(Stock);
            NamesOfAdditionalPiles.AddRange(StandardPileNames.Reserves.Take(28));

            TotalFoundations = 0;
            TotalTableaus = 4;
            TableauBuildSequence = CardFaceSequence.SameSuit;

            _dividingBehavior = new PileBehavior
            {
                Arrangement = CardArrangement.StackTopToBottom,
                AllowsPickup = Action.IsActingOnFaceUpTopCard,
                AllowsMove = delegate { return false; },
            };

            _foundationBehavior = new PileBehavior
            {
                Arrangement = ArrangeFoundation,
                AllowsPickup = Action.IsActingOnEntirePile,
                AllowsMove = action => ! action.TargetPile.IsEmpty && action.IsBuildingSameSuitAscendingRank(),
            };
        }

        public override ILayoutSite CreatePresentation(IPresentationContext presentation)
        {
            var metrics = presentation.Metrics;

            var root = presentation.CreateRowLayout();

            // Add tableau piles

            var tableau = presentation.CreateColumnLayout();

            var tableauStyle = new VisualTraits
            {
                Arrangement = CardArrangement.StackSquare,
                ContentFit = ContentFit.FitWidth,
                EmptyCue = () => EmptyPileCue.Outlined
            };

            presentation.AddPiles(tableau, NamesOfTableaus, tableauStyle);

            root.Add(tableau);
            root.Add(presentation.CreateSpacer(metrics.MaxSpacingAcross, 0));

            // Add reserve piles

            var reserveStyle = new VisualTraits
            {
                MinimumSize = new Size(metrics.CardSize.Width + metrics.StackingOffsetOnFaceUpCard.Width, metrics.CardSize.Height),
                Arrangement = ArrangeReserve,
                ContentFit = ContentFit.FitWidth,
                EmptyCue = null
            };

            for (var column = 0; column < ReserveSize.Width; ++column)
            {
                var columnLayout = presentation.CreateColumnLayout();
                columnLayout.MinimumSpacing = metrics.MinSpacingAcross;

                for (var row = 0; row < ReserveSize.Height; ++row)
                {
                    var ordinal = row * ReserveSize.Width + column;
                    columnLayout.Add(presentation.CreatePileVisual(ReserveNameFromIndex(ordinal), reserveStyle));
                }

                root.Add(columnLayout);
            }

            return root;
        }

        protected override bool IsComplete
        {
            get { return this[Waste].Count == CardFace.CardsPerStandardDeck; }
        }

        public override int PercentComplete
        {
            get { return 100 * this[Waste].Count / CardFace.CardsPerStandardDeck; }
        }

        protected override void OnTransactionEnding()
        {
            var discardActions = PilesNamed(AnyReserve)
                .Where(pile => pile.Count == CardFace.RanksPerSuit && pile.IsInSequence(CardFaceSequence.SameSuitAscendingRank))
                .Select(pile => new MovePileAction(this, pile.Bottom, new PartLocation(Waste, 0)));

            foreach (var action in discardActions)
                Execute(action);

            var pilesToSplit = PilesNamed(AnyReserve)
                .Where(pile => ! pile.IsEmpty && pile.Count % 2 == 0 && Behaviors[pile] == _dividingBehavior);

            foreach (var pile in pilesToSplit)
            {
                var sourceIndex = pile.GetOrdinal() - 1;
                var targetIndex = sourceIndex + ReserveSize.Width * (pile.Count / 4 + 1);

                var sourceLocation = new PartLocation(pile.Name, pile.Count / 2);
                var targetLocation = new PartLocation(ReserveNameFromIndex(targetIndex));

                Execute(new MovePileAction(this, sourceLocation, targetLocation));

                if (pile.Count > 1) continue;

                Execute(SwitchToFoundationAction(sourceLocation));
                Execute(SwitchToFoundationAction(targetLocation));
            }
        }

        protected override void InitializeComponents()
        {
            base.InitializeComponents();

            foreach (var pile in PilesNamed(AnyReserve))
                _reserveBehaviors.Add(pile, _dividingBehavior);
        }

        protected override void PrepareDealScript(DealScript script)
        {
            script.DealToEach(StandardPileNames.Reserves.Take(ReserveSize.Width), "7 up");
            script.DealCardToEach(NamesOfTableaus.Take(3));
        }

        protected override void OverridePlayingRules(PlayingRules playingRules)
        {
            playingRules.At(AnyReserve)
                .ConditionallyPermit<RemovePileAction>(action => Behaviors[action.SourcePile].AllowsPickup(action))
                .ConditionallyPermit<MovePileAction>(action => Behaviors[action.TargetPile].AllowsMove(action));

            playingRules.At(AnyTableau)
                .ConditionallyPermit<RemovePileAction>(Action.IsActingOnFaceUpTopCard)
                .ConditionallyPermit<MovePileAction>(action =>
                    action.IsMovingSingleCard() && action.IsBuilding(TableauBuildSequence) &&
                        (action.SourcePile.Location.Matches(AnyTableau) || Behaviors[action.SourcePile] == _dividingBehavior));
        }

        private IDictionary<CardPile, PileBehavior> Behaviors
        {
            get { return _reserveBehaviors; }
        }

        private IAction SwitchToFoundationAction(PartLocation location)
        {
            return new DelegatingAction
            {
                OnExecute = () => Behaviors[PileAt(location)] = _foundationBehavior,
                OnReverse = () => Behaviors[PileAt(location)] = _dividingBehavior
            };
        }

        private Size[] ArrangeReserve(CardPile pile, LayoutMetrics metrics)
        {
            return Behaviors.ContainsKey(pile) ? Behaviors[pile].Arrangement(pile, metrics) : new Size[] { };
        }

        private static Size[] ArrangeFoundation(CardPile pile, LayoutMetrics metrics)
        {
            var offset = new Size(metrics.StackingOffsetOnFaceDownCard.Width, 0);
            var result = new Size[pile.Count];

            if (result.Length > 1)
                result[1].Width += metrics.StackingOffsetOnFaceUpCard.Width;

            return CardArrangement.SetStackedCardPlacement(result, pile, 2, offset, offset);
        }

        private static string ReserveNameFromIndex(int n)
        {
            return StandardPileNames.PrefixForReserve + (n + 1);
        }
    }
}
